<?php
/**
 * Created by PhpStorm.
 * User: yangos
 * Date: 18-6-16
 * Time: 下午4:40
 */

namespace vola;

/**
 * App 控制所有的操作的地方
 */
class App implements \ArrayAccess {

    /**
     * @var Request
     */
    private $req;
    /**
     * @var Container
     */
    private $container;

    private $startTime, $startMem;

    private $app_debug = false, $app_error = false;

    public function __construct()
    {
        $this->container = Container::getInstance();
    }


    /**
     * 路径转换为
     * @param string $path 路径
     * @return mixed
     */
    public function path2url($path)
    {
        return str_replace([$_SERVER['DOCUMENT_ROOT'], DIRECTORY_SEPARATOR], ['', '/'], $path);
    }

    /**
     * 批量输出标准格式的数组等信息
     * @param mixed $str 打印出的字符串
     */
    public function dump(...$str)
    {
        foreach ($str as $arrrorstring) {
            // $arrrorstring = htmlspecialchars($arrrorstring);
            $pre = print_r($arrrorstring, true);
            echo '<p><pre class="yf_pre" style="font-size: 18px">' . $pre . '</pre></p>' . PHP_EOL;
        }
    }

    /**
     * 驼峰命名法转换为c命名法
     * @param string $str
     * @return null|string|string[]
     */
    public function humpToDot($str)
    {
        $str = lcfirst($str);
        if (preg_match('/([A-Z]{1})/', $str)) {
            $str = preg_replace_callback('/([A-Z]{1})/', function ($matches) {
                return '.' . strtolower($matches[0]);
            }, $str);
        }
        return $str;
    }

    /**
     * 生成model
     * @param array|string $callback
     * @return \yang\Model
     */
    public function model($callback)
    {
        if (strpos($callback, '/')) {
            $callback = explode('/', trim($callback, '/'));
        } else {
            $callback = [$callback];
        }

        if (count($callback) < 3) {
            $base = [
                $this->req->module(),
                $this->req->action(),
                $this->req->controller(),
            ];
            $callback = array_reverse($callback);
            for ($i = 2; $i >= 0; $i --) {
                $base[$i] = array_shift($callback);
                if (empty($callback)) break;
            }
            $callback = $base;
        }

        array_splice($callback, 0, 0, $this->env->get('app_name'));
        array_splice($callback, 2, 0, 'model');

        $class = implode('\\', $callback);
        return new $class;
    }

    /**
     * 根据表名创建model
     * @param string $table
     * @return \vola\Model
     */
    public function table($table)
    {
        return Db::table($table);
    }

    public function app_debug() {
        return $this->app_debug;
    }

    public function app_error() {
        return $this->app_error;
    }

    /**
     * 初次创建函数
     * @param string $request
     * @param string $startTime
     * @param string $startMem
     */
    public function create($request = '', $startTime = '', $startMem = '') {
        $this->startTime = $startTime;
        $this->startMem = $startMem;
        $this->req = !empty($request) ? $request : Container::get('request');

        $ds = DIRECTORY_SEPARATOR;
        $root = dirname(dirname(__FILE__));

        $env = [
            'root_path' => $root . $ds,
            'config_path'  => '{root_path}config'  . $ds,
            'runtime_path' => dirname($root). $ds . 'runtime' . $ds,
            'log_path' => '{runtime_path}log' . $ds,
            'template_path'=> '{runtime_path}template' . $ds,
            'cache_path'   => '{runtime_path}cache' . $ds,
            'app_path' => dirname($root) . $ds . 'app' . $ds,
            'tpl_path'    => '',
        ];
        $this->env->setArray($env);

        $env = include $root . '/config/env.php';
        $this->env->setArray($env);

        $this->app_debug = $env['app_debug'];
        $this->app_error = $env['app_error'];


        $this->config('root_url', $this->path2url($this->req->server('DOCUMENT_ROOT')));

        $this->config->setArray(include $root . '/config/config.php');

        Error::init();

        if (file_exists(dirname($root) . '/route/route.php')) {
            $this->route->register(include dirname($root) . '/route/route.php');
        }

        Loadder::add('plugin\\', $root . $ds . 'plugin' );

        if (file_exists(dirname(__DIR__) . '/helper.php')) {
            $this->includeFile(dirname(__DIR__) . '/helper.php');
            // include dirname(__DIR__) . '/../include/hooklist.php';
        }

        $this->hook->do_action('init');
    }


    /**
     * @param $name
     * @param null $value
     * @return mixed
     */
    public function config($name, $value = null) {
        if (is_array($name)) {
            $this->config->setArray($name);
            return null;
        } elseif (strpos($name, '?') === 0) {
            return $this->config->has($name);
        } elseif (strpos($name, '!') === 0) {
            return $this->config->del($name);
        } elseif ($value === null) {
            return $this->config->get($name);
        }
        $this->config->set($name, $value);
    }

    /**
     * @param $name
     * @param $value
     * @return mixed
     */
    public function env($name, $value = '') {
        if (is_array($name)) {
            $this->env->setArray($name);
            return null;
        } elseif (strpos($name, '!') === 0) {
            return $this->env->del($name);
        } elseif ($value === null) {
            return $this->env->get($name);
        }
        $this->env->set($name, $value);
    }

    public function request() {
        return $this->req;
    }
    /**
     * 引入文件
     * @param string $file 被加载的文件
     * @param array $arg 需要被转成变量的数组
     */
    public function includeFile($file, array $arg = []) {
        Loadder::includeFile($file, $arg);
    }

    /**
     * 获取return数组
     * @param string $file 被加载的文件
     * @return mixed|array
     */
    public function getReturn($file) {
        return Loadder::getContentOfFile($file);
    }

    /**
     * require形式引入数组
     * @param string $file 被加载的文件
     */
    public function requireFile($file) {
        Loadder::requireFile($file);
    }

    /**
     * 初始化
     * @param string $request
     */
    public function init($request = '') {
        $this->requset = $request ? $request : $this->requset;
    }

    /**
     * 开始运行
     * @param string $path
     */
    public function listen($path = '') {
        $app_path = rtrim( $this->env->get('app_path'), '/\\');

        if (!empty($path)) {
            $app_path = rtrim($path, '/\\');
        }

        Loadder::add('app\\', $app_path);
        $this->env('app_path', $app_path);
        $base = [
            $this->config('route.base.module'),
            $this->config('route.base.controller'),
            $this->config('route.base.action'),
        ];

        $runner = $this->route->listen($this->req->path(), $base);
        $data = $this->runCall($runner);

        $this->hook->do_action('app_init');

        if ($data instanceof Response) {
            $data->send();
        } else {
            $data = Container::get('response')->head($data, 200);

            $data->content_type()->send();
        }
    }

    public function getRuntimepath() {
        return $this->env->get('log_path');
    }

    public function getTplRuntimepath() {
        return rtrim($this->env->get('template_path'), '\\/');
    }

    public function debug() {
        $this->log('Url', $this->getNowUrl(), 'debug');
        $this->log('UseMemory', $this->getUseMem() . ' kb', 'debug');
        $this->log('UseTime', $this->getUseTime() . ' s', 'debug');
        $this->log('IncludeCount', $this->getIncludeFileNum(), 'debug');
        $this->log('Requests', $this->getThroughputRate() . ' req/s', 'debug');
    }

    /**
     * 当前URL
     * @return mixed
     */
    private function getNowUrl()
    {
        return $_SERVER["REQUEST_URI"];
    }

    /**
     * 使用掉的内存
     * @return string
     */
    private function getUseMem()
    {
        return number_format((memory_get_usage() - $this->startMem) / 1024, 2);
    }

    /**
     * 引入的文件数量
     * @return int
     */
    private function getIncludeFileNum()
    {
        return count(get_included_files());
    }

    /**
     * 执行的时间
     * @return string
     */
    private function getUseTime()
    {
        return number_format(round(microtime(true) - $this->startTime, 10), 6);
    }

    /**
     * 获取当前访问的吞吐率情况
     * @return string
     */
    private function getThroughputRate()
    {
        return number_format(1 / $this->getUseTime(), 2);
    }

    public function log($name, $message = '', $level = 'info', $context = []) {
        return Container::get('log')->recore($name, $message, $level, $context);
    }

    public function endapp() {
        if ($this->app_debug === true) {
            $this->debug();
        }
        Container::get('log')->save();
    }

    private function runCall($runner) {
        
        $function = explode('/', $runner['function']);
        $action = lcfirst(array_pop($function));
        $controller = array_pop($function);

        $this->req->controller($controller);
        $this->req->module(implode('/', $function));
        $this->req->action($action);

        $function[] = $controller;
        array_splice($function, -1, 0, 'controller');
        array_unshift($function,  'app');

        $classname = '\\' . implode('\\', $function);
        $class = new $classname;
        if (!method_exists($class, $action)) {
            Error::the('Route', ' Class ' . $classname . ' Not Found method ' . $action . ' ! ');
        }
        return call_user_func_array([$class, $action], $runner['args']);
    }

    public static function runStatic($runner) {
        return Container::get('app')->runCall($runner);
    }


    public function __set($name, $value)
    {
        $this->container->bind($name, $value);
    }

    public function __get($name)
    {
        return $this->container->make($name);
    }

    public function __isset($name)
    {
        return $this->container->bound($name);
    }

    public function __unset($name)
    {
        $this->container->delete($name);
    }

    public function offsetExists($key)
    {
        return $this->__isset($key);
    }

    public function offsetGet($key)
    {
        return $this->__get($key);
    }

    public function offsetSet($key, $value)
    {
        $this->__set($key, $value);
    }

    public function offsetUnset($key)
    {
        $this->__unset($key);
    }
}
